module dice.render.shape;

import dice.render.painter;
import dice.render.structs;

/** A shape */
class Shape
{
	/** Gets a rectangle that covers this shape */
	abstract Rect bounds ();
	/** Sets the bounding rectangle if possible (usually involves stretching the shape) */
	abstract void bounds (Rect r);
	
	/** Trace the shape onto given painter */
	abstract void traceOn (Painter p);
}

/** A shape given by a rectangle */
class Rectangle : Shape
{
	/** The co-ordinates of it */
	Rect r;
	
	Rect bounds ()
	{
		return r;
	}
	
	void bounds (Rect x)
	{
		r = x;
	}
	
	/** Constructor */
	this (float x1, float y1, float x2, float y2)
	{
		r = Rect(x1,y1,x2,y2);
	}
	
	void traceOn (Painter p)
	{
		p.move(r.x1,r.y1);
		p.line(r.x2,r.y1);
		p.line(r.x2,r.y2);
		p.line(r.x1,r.y2);
		p.close();
	}
}

/** Something on which a path can be traced */
interface Tracer
{
	/** Add a line to given x and y co-ordinates */
	void line (float x, float y);
	
	/** Add a move to given x and y co-ordinates */
	void move (float x, float y);
	
	/** Add a curve */
	void curve (float x1, float y1, float x2, float y2, float x, float y);
	
	/** Close the path */
	void close ();
}

/** A complex shape or series of lines */
class Path : Shape, Tracer
{
	/** Get the bounding rectangle of this path */
	Rect bounds ()
	{
		if (nodes.length == 0)
			return Rect(0,0,0,0);
		
		Rect r = Rect(nodes[0].x, nodes[0].y, nodes[0].x, nodes[0].y);
		
		foreach (Node n; nodes)
			if (n.type != NodeType.Close)
			{
				if (r.x1 > n.x)
					r.x1 = n.x;
				if (r.y1 > n.y)
					r.y1 = n.y;
				if (r.x2 < n.x)
					r.x2 = n.x;
				if (r.y2 < n.y)
					r.y2 = n.y;
			}
		
		return r;
	}
	
	/** Reshapes this path to fit in the given rectangle */
	void bounds (Rect r)
	{
	}
	
	/** Creates a path based on another */
	this (Path p)
	{
		nodes = p.nodes[0..p.nodes.length];
	}
	
	/** Creates a path */
	this ()
	{
	}
	
	/** The type of a node */
	enum NodeType
	{
		Partial,	/// Part of the last node
		Move,		/// Move to
		Line,		/// Line to
		Curve,		/// Curve to
		Close		/// Close the path
	}
	
	/** A single element or part thereof of a path */
	struct Node
	{
		NodeType type;	/// The type
		float x, y;		/// X and Y co-ordinates
	}
	
	/** This is where the nodes are stored */
	Node[] nodes;
	
	/** Add a line to given x and y co-ordinates */
	void line (float x, float y)
	{
		nodes ~= Node(NodeType.Line, x, y);
	}
	
	/** Add a move to given x and y co-ordinates */
	void move (float x, float y)
	{
		nodes ~= Node(NodeType.Move, x, y);
	}
	
	/** Add a curve */
	void curve (float x1, float y1, float x2, float y2, float x, float y)
	{
		nodes ~= Node(NodeType.Curve, x1, y1);
		nodes ~= Node(NodeType.Partial, x2, y2);
		nodes ~= Node(NodeType.Partial, x, y);
	}
	
	/** Close the path */
	void close ()
	{
		nodes ~= Node(NodeType.Close, 0, 0);
	}
	
	/** Trace onto a painter */
	void traceOn (Painter p)
	{
		for (int i = 0; i<nodes.length; i++)
		{
			if (nodes[i].type == NodeType.Move)
				p.move(nodes[i].x, nodes[i].y);
			else if (nodes[i].type == NodeType.Line)
				p.line(nodes[i].x, nodes[i].y);
			else if (nodes[i].type == NodeType.Curve)
			{
				p.curve(nodes[i].x, nodes[i].y, nodes[i+1].x, nodes[i+1].y, nodes[i+2].x, nodes[i+2].y);
				i += 2;
			}
			else if (nodes[i].type == NodeType.Close)
				p.close();
		}
	}
}
